package com.cacho.s2b.lesson.cucumber;

import com.cacho.s2b.lesson.assertion.Assertion;
import com.cacho.s2b.lesson.engine.*;
import com.cacho.s2b.lesson.vo.*;

import io.cucumber.java.Scenario;
import io.cucumber.java.Before;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import io.restassured.http.Method;
import io.restassured.response.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringSubstitutor;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class MyTestSteps extends cucumberStep {
    private static final String STEP_NAME = "STEP_NAME"; // 特殊请求参数时标记
    private static final String SKIP_IF_VAR_EXISTS = "SKIP_IF_VAR_EXISTS"; // 特殊请求参数时标记
    private static final List<String> RESERVED_KEYS = new ArrayList<>(); // 预留的字段
    private static final ApiInfoHub API_INFO_HUB = ApiInfoHub.getInstance(); // 初始化配置文件
    private static final ApiMetaInfo META_INFO = API_INFO_HUB.getEnvInfo().getApiMetaInfo(); // 初始化配置文件
    private static final Map<String, ApiRunInfo> RUN_MAP = API_INFO_HUB.getApiRunInfoMap();
    private static final String SAMPLE_FOLDER = META_INFO.getSampleFolder();
    private ApiTestSender sender;
    private ApiTestInfo tester;
    private Map<String, String> headers = new HashMap<>(); // 配置文件中的公共headers
    private String scenarioName;
    private URI featureUri;  //URI(Uniform Resource Identify)，统一资源标识符，是一个用于标识某一互联网资源名称的字符串

    // 空构造函数赋值
    public MyTestSteps() {
        RESERVED_KEYS.add(STEP_NAME);
        RESERVED_KEYS.add(SKIP_IF_VAR_EXISTS);
    }

    @Before()
    public void before_scenario(Scenario scenario) {
        // 对 ApiTestSender 赋值
        this.sender = super.getSender(scenario.getId());
        // 对 ApiTestInfo 赋值
        this.tester = this.sender.getTester();
        this.tester.setScenario(scenario);
        this.tester.setEnvName(API_INFO_HUB.getEnvInfo().getName());
        // 对测试场景赋值
        this.scenarioName = scenario.getName();
        this.featureUri = scenario.getUri();
        log.info("运行测试场景{} 在feature文件地址{}", scenarioName, featureUri);
    }

    @When("^Reset headers$")
    public void reset_headers() {
        this.headers.clear();
    }

    // 对请求头headers 赋值
    @When("^Specify headers as below table$")
    public void Specify_headers_as_below_table(Map<String, String> headers) {
        Map<String, String> map = addEnvHeadersToApiHeaders(headers); // 汇总的headers
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String k = entry.getKey();
            // key为空或null 跳过
            if (StringUtils.isEmpty(k)) {
                continue;
            }
            String v = entry.getValue();
            ApiExpressEngine engine = new ApiExpressEngine(this.sender.getTester());
            v = engine.translate(v).toString();
            this.headers.put(k, v);
        }
    }

    // 公共的headers添加到接口请求headers里面
    private Map<String, String> addEnvHeadersToApiHeaders(Map<String, String> map) {
        Map<String, String> envHeaders = API_INFO_HUB.getEnvInfo().getHeaders();
        Map<String, String> apiHeaders = new HashMap<>();
        if (!envHeaders.isEmpty()) {
            apiHeaders.putAll(envHeaders);
            apiHeaders.putAll(map);
        }
        return apiHeaders;
    }

    // 读取 接口 body数据 和 jwt body数据
    @And("^Read API '(.*)' payload with updated below fields$")
    public void read_API_payload_with_updated_below_fields(String apiDefName, Map<String, String> updatedFields) {
        String stepName = updatedFields.getOrDefault(STEP_NAME, "");
        // engine.ApiTestSender
        sender.nameAStep(stepName);
        // vo.ApiRunInfo
        ApiRunInfo runInfo = RUN_MAP.getOrDefault(apiDefName, null);
        if (runInfo == null) {
            Assertion.fail(String.format("无法找到该接口定义名：%s", apiDefName));
        }
        // 省略了
        String category = runInfo.getCategory();
        String serverUrl = API_INFO_HUB.getEnvInfo().getServers().getOrDefault(category, "");
        if ("".equals(serverUrl)) {
            Assertion.fail(String.format("微服务%s不在该%s测试环境下", category, API_INFO_HUB.getEnvInfo().getName()));
        }
        String basicPayloadFromSample = "";
        try {
            basicPayloadFromSample = getPayloadFromSample(runInfo);
        } catch (Exception e) {
            Assertion.fail(String.format("Payload sample is not exist: \n%s", e.getMessage()));
        }
        ApiExpressEngine express = new ApiExpressEngine(this.sender.getTester());
        Map<String, String> jsonKeyMap = new HashMap<>();
        Map<String, String> jwtDataMap = new HashMap<>();
        ApiStepInfo lastStepInfo = this.sender.getTester().getLastStepInfo();
        lastStepInfo.setJwtDataMap(jwtDataMap);
        for (Map.Entry<String, String> entry : updatedFields.entrySet()) {
            String k = entry.getKey();
            String v = entry.getValue();
            if (k.startsWith("JWT_")){
                k = k.substring("JWT_".length());
                String value = express.translate(v).toString();
                jwtDataMap.put(k,value);
            }else if (StringUtils.isNotEmpty(basicPayloadFromSample)) {
                String path = k;
                PathType pathType = checkPathType(path);
                if (PathType.JSON_PATH.equals(pathType)) {
                    basicPayloadFromSample = updateJsonByExpress(express, basicPayloadFromSample, path, v);
                }
            }
        }
        RequestInfo request = new RequestInfo();
        request.setBody(basicPayloadFromSample);
        lastStepInfo.setRequest(request);
    }

    @And("^Send API '(.*)' with updated below fields$")
    public void send_API_with_updated_below_fields(String apiDefName, Map<String, String> updatedFields) {
        //
        String stepName = updatedFields.getOrDefault(STEP_NAME, "");
        // engine.ApiTestSender
        sender.nameAStep(stepName);
        // vo.ApiRunInfo
        ApiRunInfo runInfo = RUN_MAP.getOrDefault(apiDefName, null);
        if (runInfo == null) {
            Assertion.fail(String.format("无法找到该接口定义名：%s", apiDefName));
        }
        // 省略了
        ApiActivation activation = API_INFO_HUB.getActivationMap().getOrDefault(this.headers.get("GroupId"),null);
        String category = runInfo.getCategory();
        String mapping = runInfo.getMapReq();
        String method = runInfo.getMethod();
        String serverUrl = API_INFO_HUB.getEnvInfo().getServers().getOrDefault(category, "");
        if ("".equals(serverUrl)) {
            Assertion.fail(String.format("微服务%s不在该%s测试环境下", category, API_INFO_HUB.getEnvInfo().getName()));
        }

        String fullUrl = serverUrl + runInfo.getPath();
        log.debug("接口请求的完整URL: {}", fullUrl);
        String basicPayloadFromSample = "";
        try {
            basicPayloadFromSample = getPayloadFromSample(runInfo);
        } catch (Exception e) {
            Assertion.fail(String.format("Payload sample is not exist: \n%s", e.getMessage()));
        }
        // 省略了通过api_def.csv获取请求body部分
        // 对 vo.ApiStepInfo 赋值:name/apiDefName/request
        ApiStepInfo lastStepInfo = this.sender.getTester().getLastStepInfo();
        lastStepInfo.setApiDefName(apiDefName);
        try {
            prepareRequestByUpdatedFiles(updatedFields, basicPayloadFromSample, category, mapping, method, fullUrl,activation);
            sender.sendApiRequest(lastStepInfo.getRequest(), apiDefName);
        } catch (Exception e) {
            Assertion.fail(e.getMessage());
        }
    }

    // 获取 接口发送的sample payload
    private String getPayloadFromSample(ApiRunInfo runInfo) throws IOException {
        String sampleName = runInfo.getSample(); // sample 文件名.json
        String categoryName = runInfo.getCategory(); // 接口分类
        if (StringUtils.isEmpty(sampleName)) {
            return null;
        }
        String resPath = StringUtils.isEmpty(categoryName) ?
                String.format("/%s/%s", SAMPLE_FOLDER, sampleName) :
                String.format("/%s/%s/%s", SAMPLE_FOLDER, categoryName, sampleName);
        InputStream in = this.getClass().getResourceAsStream(resPath);
        return IOUtils.toString(in, Constants.UTF8);
    }

    // 根据请求方法赋默认值，然后根据传的updatedFiles进行payload处理，mapping进行处理
    private void prepareRequestByUpdatedFiles(Map<String, String> updatedFields, String payload, String category,
                                              String mapping, String method, String apiUrl,ApiActivation activation) throws Exception {
        RequestInfo request = switch (method.toLowerCase()) {
            case "get" -> RequestInfo.getRequest(apiUrl);
            case "post" -> RequestInfo.postRequest(apiUrl);
            default -> throw new Exception("无效的接口请求method: " + method);
        };
        ApiStepInfo lastStepInfo = sender.getTester().getLastStepInfo();
        lastStepInfo.setRequest(request);
        request.setHeaders(this.headers);
        request.setActivation(activation);
        try {
            parseApiFields(payload, updatedFields, category, mapping);

        } catch (Exception e) {
            throw new Exception(String.format("Update payload fail:\n%", e.getMessage()));
        }
    }

    // mapping=oil.csv
    private void parseApiFields(String payload, Map<String, String> updatedFields, String category, String mapping) throws MalformedURLException {
        RequestInfo request = this.sender.getTester().getLastStepInfo().getRequest();
        ApiExpressEngine express = new ApiExpressEngine(this.sender.getTester());
        Map<String, String> jsonKeyMap = new HashMap<>();
        Map<String, String> urlMap = new HashMap<>();  // endpoint url 组合
        Map<String, Object> formDataMap = new HashMap<>(); // form 类型请求参数
        for (Map.Entry<String, String> entry : updatedFields.entrySet()) {
            String k = entry.getKey();
            String v = entry.getValue();
            // 如果 feature 文件传过来的 updatedFields 参数，key值为空或者包含在特殊参数内，跳过该循环
            if (RESERVED_KEYS.contains(k) || StringUtils.isEmpty(k)) {
                continue;
            }
            // 如果参数以 URL_ 开头， 当成 Endpoint 的url
            if (k.startsWith("URL_")) {
                k = k.substring("URL_".length());
                // val = getValueFromExpress(express, val); 待定：tt
                urlMap.put(k, v);
            }
            if (Method.GET.equals(request.getMethod())) {
                urlMap.put(k, v); // feature文件：?keyword=股票
            }
            // body 是 form data类型
            if (k.startsWith("FROM_")) {
                k = k.substring("FROM_".length());
                Object obj = express.translate(v);
                formDataMap.put(k, obj);
            } else if (StringUtils.isNotEmpty(request.getBody())) {
                String path = k;
                PathType pathType = checkPathType(path);
                if (PathType.JSON_PATH.equals(pathType)) {
                    payload = updateJsonByExpress(express, payload, path, v);
                }
            }
        }
        StringSubstitutor sub = new StringSubstitutor(urlMap, "{", "}");
        String url = request.getUrl().toString(); // 格式 /dy/keylead/{keyword}
        url = sub.replace(url); // 替换url里面的成为 ?keyword=股票
        request.setUrl(new URL(url));
        request.setBody(payload);
        request.setFormDataMap(formDataMap);
    }

    private PathType checkPathType(String path) {
        PathType temp = PathType.MAPPING_PATH;
        int pathTypeFlag = "$/".indexOf(path.substring(0, 1)); // 判断参数第一字符在字符串 ”$/“的位置
        switch (pathTypeFlag) {
            case 0:
                temp = PathType.JSON_PATH; // $
                break;
            case 1:
                temp = PathType.XPATH; // /
                break;
        }
        return temp;
    }

    // 根据json格式路径对请求参数进行处理（新增，删除，更新）
    private String updateJsonByExpress(ApiExpressEngine express, String payload, String path, String val) {
        ValueParser vp = new ValueParser(express, val);
        vp.parse(); // 数据处理和存储
        ValueOperation vOp = vp.getValueOperation();
        JsonOperator jsOp = new JsonOperator(vOp);
        return jsOp.processJson(payload, path);
    }

    @Then("^Response status code should be (\\d+)$")
    public void response_status_code_should_be(int expectedStatusCode) {
        ApiStepInfo stepInfo = sender.getTester().getLastStepInfo();
        Response response = stepInfo.getResponse();
        int statusCode = response.statusCode();
        Assertion.assertEquals(statusCode, expectedStatusCode,
                String.format("请查看返回的body信息：%s", response.getBody().asPrettyString()));
    }

    @When("^Assert below expression$")
    public void assert_below_expression(List<Map<String, String>> expressions) {
        ApiExpressEngine express = new ApiExpressEngine(sender.getTester());
        for (Map<String, String> exp : expressions) {
            String actual = exp.getOrDefault("actual", "");
            String actualExp = exp.getOrDefault("actual", ""); // 真实的表达式
            String expected = exp.getOrDefault("expected", "");
            String relation = exp.getOrDefault("relation", "==");
            String msg = exp.getOrDefault("msg", "");
            String condition = exp.getOrDefault("condition", "");
            //对接口字段预期值和实际值进行特殊处理
            actual = express.translate(actual).toString(); // 获取接口response的字段值
            switch (relation.toUpperCase()) {
                case "CONTAINS":
                    Assertion.assertContains(actual, expected, msg); // 不包含
                    break;
                case "NOT":
                    Assertion.assertNotEquals(actual, expected, msg); // 不相等
                    break;
                default:
                    if (StringUtils.isEmpty(condition) || !condition.equalsIgnoreCase(actual)) {
                        Assertion.assertSameResult(actual, expected, msg);
                    }
            }
        }
    }

    @And("^Delay (\\d+) seconds$")
    public void delay_seconds(int delay) throws InterruptedException {
        long milSec = delay > 0 ? delay * 1000L : delay * (-1000L);
        Thread.sleep(milSec);
    }
}
